package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.utils.AssertUtils;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.entity.PointsBoard;
import com.tianji.learning.entity.PointsBoardSeason;
import com.tianji.learning.mapper.PointsBoardMapper;
import com.tianji.learning.pojo.PointsBoardQuery;
import com.tianji.learning.service.IPointsBoardSeasonService;
import com.tianji.learning.service.IPointsBoardService;
import com.tianji.learning.service.IPointsRecordService;
import com.tianji.learning.vo.PointsBoardItemVO;
import com.tianji.learning.vo.PointsBoardVO;
import com.xxl.job.core.context.XxlJobHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.tianji.common.constants.Constant.POINTS_BOARD_TABLE_PREFIX;

/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 * @author xjs
 */
@Slf4j
@Service
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {

    @Autowired
    private IPointsRecordService recordService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private UserClient userClient;
    @Autowired
    private IPointsBoardSeasonService seasonService;

    @Override
    public void clearRedisPointBoard() {
        // 上个月的时间
        LocalDateTime prevMonth = LocalDateTime.now().minusMonths(1);
        // 构造redis的key
        String key = recordService.getBoardKey(prevMonth);
        // 删除redis，key里面的元素比较多，直接delete可能会阻塞
        // 同步删除
        //redisTemplate.delete(key);
        // 异步删除
        redisTemplate.unlink(key);
    }

    @Override
    public void syncRedisToMysql() {
        // 获取执行任务的节点的总个数
        int shardTotal = XxlJobHelper.getShardTotal();
        // 当前节点的索引值
        int shardIndex = XxlJobHelper.getShardIndex();
        // 上个月的时间
        LocalDateTime prevMonth = LocalDateTime.now().minusMonths(1);
        // 根据上个月的时间，查询赛季
        PointsBoardSeason season = seasonService.getByDate(prevMonth);
        if(season == null){
            log.error("查询不到赛季");
            return;
        }
        // 计算数据库的表名
        String tableName  = POINTS_BOARD_TABLE_PREFIX + season.getId();
        // 构造redis的key
        String key = recordService.getBoardKey(prevMonth);
        // 分页读取redis的历史榜单
        int pageNo = (shardIndex + shardTotal) > shardTotal?shardIndex:(shardIndex + shardTotal);
        int pageSize = 2;
        while(true){
            log.info("pageNo:{}", pageNo);
            // 读取一页数据
            List<PointsBoardItemVO> list = pageQueryRedisBoard(pageNo, pageSize, key, false);
            if(CollectionUtils.isEmpty(list)){
                break;
            }
            // 批量写入mysql
            List<PointsBoard> boardList = BeanUtils.copyList(list, PointsBoard.class);
            // this.saveBatch(boardList); 加上MP的动态表名的插件
            // 需要手动设置id，因为这个任务时并发执行的，不能再依赖数据库的自增id
            boardList.stream().forEach(b->b.setId(b.getRank().longValue()));
            this.baseMapper.saveBatch(tableName, boardList);
            //
            pageNo  = pageNo + shardTotal;
        }
    }

    @Override
    public void createTable(String tableName) {
        this.baseMapper.createTable(tableName);
    }

    @Override
    public PointsBoardVO pageQuery(PointsBoardQuery query) {
        Long userId = UserContext.getUser();
        Long season = query.getSeason();
        PointsBoardVO resultVo = new PointsBoardVO();
        if(season == null || season <= 0){
            // 当前赛季， 当前赛季，数据是在redis的zset中
            // 我的积分和排名
            String boardKey = recordService.getBoardKey(LocalDateTime.now());
            // 获取用户的积分
            Double myScore = redisTemplate.opsForZSet().score(boardKey, "" + userId);
            resultVo.setPoints(myScore == null?0:myScore.intValue());
            // 获取用户的排名
            Long myRank = redisTemplate.opsForZSet().reverseRank(boardKey, "" + userId);
            resultVo.setRank(myRank==null?-1:myRank.intValue()+1);
            // 分页的积分和排名
            List<PointsBoardItemVO> itemList = pageQueryRedisBoard(query.getPageNo(), query.getPageSize(), boardKey, true);
            resultVo.setBoardList(itemList);
            return resultVo;
        }else{
            // 历史赛季

        }
        // 组装返回结果
        return null;
    }

    private List<PointsBoardItemVO> pageQueryRedisBoard(int pageNo, int pageSize, String boardKey, boolean queryUserName){
        // 当前页的起始索引值
        long start = (pageNo -1 ) * pageSize;
        // 当前页的结束索引值
        long end = pageNo * pageSize - 1;
        // 查询zset中从start到end的value列表，携带score
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet()
                .reverseRangeWithScores(boardKey, start, end);
        // 转化成VO
        List<PointsBoardItemVO> boardList = new ArrayList<>();
        if(!CollectionUtils.isEmpty(typedTuples)){
            Map<String, String> userMap = new HashMap<>();
            // 根据用户id远程feign查询用户
            if(queryUserName){
                List<Long> userIds = typedTuples.stream().map(t -> t.getValue()).map(Long::parseLong).collect(Collectors.toList());
                List<UserDTO> users = userClient.queryUserByIds(userIds);
                AssertUtils.isNotNull(users, "用户信息不能为空");
                userMap = users.stream().collect(Collectors.toMap(u->u.getId().toString(), u -> u.getName()));
            }
            // 当前页的数据的起始排名 = 当前页数据的索引值 + 1
            int rankStart = (int)(start + 1);
            for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
                PointsBoardItemVO vo = new PointsBoardItemVO();
                String userIdString = typedTuple.getValue();
                Double score = typedTuple.getScore();
                vo.setName(userMap.get(userIdString));
                vo.setRank(rankStart++);
                vo.setPoints(score.intValue());
                vo.setUserId(Long.parseLong(userIdString));
                boardList.add(vo);
            }
        }
        return boardList;
    }
}
